#ifndef UUID_AE6DCC34681F4D7B1AFCA29F70AD44DC
#define UUID_AE6DCC34681F4D7B1AFCA29F70AD44DC

#include <gvl/support/cstdint.hpp>
#include <gvl/sockets/sockets.hpp>


namespace Psync5
{

struct PacketHeader
{
	enum Type
	{
		Init = 0,
		InitAck = 1,
		Cookie = 2,
		CookieAck = 3,
		Normal = 4,
		Shutdown = 5,
		ShutdownAck = 6,
		ShutdownComplete = 7
	};

	PacketHeader(Type type, uint32_t id)
	: id(id)
	, type(type)
	{
	}

	uint32_t id;
	Type type;
};

// TODO: Bounds-checking on reading.
// When writing, it's assumed that the caller checks bounds.

struct Packet
{
	typedef uint8_t Byte;

	Packet(Byte* begin, Byte* end)
	: begin(begin)
	, pos(begin)
	, end(end)
	{
	}

	Packet(Byte* begin, Byte* pos, Byte* end)
	: begin(begin)
	, pos(pos)
	, end(end)
	{
	}

	void writeHeader(PacketHeader h)
	{
		// type:8 id:16 _:8
		write8(h.type);
		write16(h.id);
	}

	PacketHeader readHeader()
	{
		uint32_t type = read8();
		uint32_t id = read16();
		return PacketHeader(PacketHeader::Type(type), id);
	}

	void write8(uint8_t w)
	{
		*pos++ = w;
	}

	void write16(uint32_t w)
	{
		*reinterpret_cast<uint16_t*>(pos) = uint16_t(w); // !arch
		pos += 2;
	}

	void write24(uint32_t w)
	{
		write8(w & 0xff);
		write8((w >> 8) & 0xff);
		write8((w >> 16) & 0xff);
	}

	void write32(uint32_t w)
	{
		*reinterpret_cast<uint32_t*>(pos) = w; // !arch
		pos += 4;
	}

	void write64(uint64_t w)
	{
		*reinterpret_cast<uint64_t*>(pos) = w; // !arch
		pos += 8;
	}

	void write(uint8_t const* p, std::size_t len)
	{
		std::memcpy(pos, p, len);
		pos += len;
	}

	void writeDynUint(uint32_t v)
	{
		if(v < (1<<7))
		{
			write8(uint8_t(v));
		}
		else if(v < (1<<7) + (1<<14))
		{
			v -= (1<<7);
			write8(0x80 | (v >> 7));
			write8(v & 0x7f);
		}
		else
		{
			v -= (1<<7) + (1<<14);
			sassert(v < (1<<21));
			write8(0x80 | (v >> 14));
			write8(0x80 | (v >> 7));
			write8(v & 0x7f);
		}
	}

	void writeDynInt(int32_t v)
	{
		if(v < 0) writeDynUint((uint32_t(-1-v) << 1) | 1);
		else writeDynUint(uint32_t(v) << 1);
	}

	uint32_t* reserve32()
	{
		uint32_t* ret = reinterpret_cast<uint32_t*>(pos);
		pos += 4;
		return ret;
	}

	uint32_t read8()
	{
		return *begin++;
	}

	uint32_t read16()
	{
		uint32_t w = *reinterpret_cast<uint16_t*>(begin);
		begin += 2;
		return w;
	}

	uint32_t read24()
	{
		uint32_t w = read8();
		w |= read8() << 8;
		w |= read8() << 16;
		return w;
	}

	uint32_t read32()
	{
		uint32_t w = *reinterpret_cast<uint32_t*>(begin);
		begin += 4;
		return w;
	}

	uint64_t read64()
	{
		uint64_t w = *reinterpret_cast<uint64_t*>(begin);
		begin += 8;
		return w;
	}

	int32_t readDynInt()
	{
		uint32_t v = readDynUint();
		if(v & 1) return -1 - int32_t(v >> 1);
		else return int32_t(v >> 1);
	}

	uint32_t readDynUint()
	{
		uint32_t v = read8();

		if(v < (1<<7))
			return v;
		
		v -= (1<<7);
		uint32_t b = read8();

		if(b < (1<<7))
			return (v << 7) | b;

		b -= (1<<7);
		uint32_t b2 = read8();

		return (v << 14) | (b << 7) | b2;
	}

	void read(uint8_t* p, std::size_t len)
	{
		std::memcpy(p, begin, len);
		begin += len;
	}

	int trySend(gvl::socket sock, gvl::internet_addr const& addr) const
	{
		return sock.sendto(begin, size() * sizeof(Byte), addr);
	}

	int spaceLeft()
	{ return int(end - pos); }

	int size() const
	{ return int(pos - begin); }
	
	Byte* begin;
	Byte* pos;
	Byte* end;
};

struct PacketStorage : Packet
{
	PacketStorage()
	: Packet(data, data + 1500)
	{
	}

	uint8_t data[1500];
};

}

#endif // UUID_AE6DCC34681F4D7B1AFCA29F70AD44DC
